﻿/*******************************************************************************
 * You may amend and distribute as you like, but don't remove this header!
 *
 * PblReader provides the ability to read PowerBuilder PBL files to extract source code.
 * See http://pblreader.codeplex.com/ for details.
 *
 * Copyright (C) 2012  Wagner DosAnjos
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU Lesser General Public License for more details.
 *
 * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
 * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
 *
 * All code and executables are provided "as is" with no warranty either express or implied. 
 * The author accepts no liability for any damage or loss of business that this product may cause. 
 *******************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Text.RegularExpressions;

namespace Pbl.Parser
{
    public static class SrdParser
    {
        private enum ParseState { Tag, SubTag, Equal, Value }

        private static Dictionary<ParseState, Regex> PARSER = new Dictionary<ParseState, Regex>()
	    {
		    {ParseState.Tag, new Regex(@"([\w\.]+)|([\)]{1})", RegexOptions.Singleline)},
		    {ParseState.SubTag, new Regex(@"([\w\.]+)|([\)]{1})", RegexOptions.Singleline)},
		    {ParseState.Equal, new Regex(@"(\(|\)|\=(?(\(\()|\(?))", RegexOptions.Singleline)},
		    {ParseState.Value, new Regex("(?(\")\"(?(~)~.|.)*?\"|(?(\\()\\(.+?\\)\\)|(\\w+\\(\\d+\\)|[^\\s\\)]+)))", RegexOptions.Singleline)},
	    };

        private static Regex LIST_PARSER = new Regex(@"\([^\(\)]+?\)", RegexOptions.Singleline);
        private static Regex ITEM_PARSER = new Regex(@"\w+", RegexOptions.Singleline);

        public static XmlDocument Parse(string content)
        {
            var srd = new XmlDocument();

            srd.AppendChild(srd.CreateElement("srd"));

            Parse(content.Substring(content.IndexOf("\r\n") + 2), // skip first line ("release 10.5;")
                  srd.DocumentElement
                 );

            return srd;
        }

        private static XmlDocument Parse(string content, XmlNode node)
        {
            var doc = node.OwnerDocument;
            var state = ParseState.SubTag;
            Match match;
            
            try
            {
                for (int position = 0; position < content.Length; position = match.Index + match.Length)
                {
                    match = PARSER[state].Match(content, position);

                    //Console.WriteLine("{0}: {1}", state, match.Value);

                    if (match.Value == String.Empty)
                    {
                        break;
                    }

                    if (state == ParseState.Tag)
                    {
                        if (match.Value == ")")
                        {
                            node = node.ParentNode;
                            state = ParseState.Tag;
                        }
                        else
                        {
                            node = node.ParentNode.AppendChild(doc.CreateElement(match.Value));
                            state = ParseState.Equal;
                        }
                    }
                    else if (state == ParseState.SubTag)
                    {
                        if (match.Value == ")")
                        {
                            if (node.HasChildNodes)
                            {
                                node = node.ParentNode;
                            }

                            state = ParseState.Tag;
                        }
                        else
                        {
                            node = node.AppendChild(doc.CreateElement(match.Value));
                            state = ParseState.Equal;
                        }
                    }
                    else if (state == ParseState.Equal)
                    {
                        state = ParseState.Value;

                        if (match.Value == "=")
                        {
                            state = ParseState.Value;
                        }
                        else if (match.Value == ")")
                        {
                            var parent = node.ParentNode;

                            parent.RemoveChild(node);

                            parent.InnerText = node.Name;

                            node = parent;

                            state = ParseState.Tag;
                        }
                        else
                        {
                            state = ParseState.SubTag;
                        }
                    }
                    else if (state == ParseState.Value)
                    {
                        if (match.Value.StartsWith("(("))
                        {
                            var listMatches = LIST_PARSER.Matches(match.Value);

                            foreach (Match listMatch in listMatches)
                            {
                                var itemMatches = ITEM_PARSER.Matches(listMatch.Value);

                                var item = node.AppendChild(doc.CreateElement("item"));

                                item.InnerText = itemMatches[0].Value;

                                if (itemMatches.Count > 1)
                                {
                                    var type = doc.CreateAttribute("type");

                                    type.Value = itemMatches[1].Value;

                                    item.Attributes.Append(type);
                                }
                            }
                        }
                        else
                        {
                            var value = (match.Value.StartsWith("\""))
                                      ? match.Value.Substring(1, match.Length - 2).Replace("~\"", "\"").Replace("~~", "~")
                                      : match.Value;

                            if (node.Name == "retrieve" && value.StartsWith("PBSELECT"))
                            {
                                Parse(value, node);
                            }
                            else
                            {
                                node.InnerText = value;
                            }
                        }

                        state = ParseState.Tag;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Srd Parse error. " + ex.Message, ex);
            }

            return doc;
        }
    }
}
